﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Data;

namespace Slex.Lib.Interactions
{
    /// <summary>
    /// The base class for all Slex framework triggers
    /// </summary>
    public abstract class SlexTrigger : System.Windows.Interactivity.TriggerBase<DependencyObject>
    {
        protected bool initialized = false;


        #region Properties


        /// <summary>
        /// The data context of the element
        /// </summary>
        public object Model
        {
            get
            {
                if (AssociatedObject is FrameworkElement)
                    return (AssociatedObject as FrameworkElement).DataContext;
                else
                    return null;
            }
        }


  
        /// <summary>
        /// The type of the source
        /// </summary>
        public ContextTypeEnum SourceType
        {
            get { return (ContextTypeEnum)GetValue(SourceTypeProperty); }
            set { SetValue(SourceTypeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SourceType.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SourceTypeProperty =
            DependencyProperty.Register("SourceType", typeof(ContextTypeEnum), typeof(SlexTrigger), new PropertyMetadata(ContextTypeEnum.Element));

        


        /// <summary>
        /// Name of the property to listen
        /// </summary>
        public string ElementName
        {
            get { return (string)GetValue(ElementNameProperty); }
            set { SetValue(ElementNameProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ElementName.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ElementNameProperty =
            DependencyProperty.Register("ElementName", typeof(string), typeof(SlexTrigger), new PropertyMetadata("This"));

        public string TriggerName
        {
            get { return (string)GetValue(TriggerNameProperty); }
            set { SetValue(TriggerNameProperty, value); }
        }

        // Using a DependencyProperty as the backing store for TriggerName.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TriggerNameProperty =
            DependencyProperty.Register("TriggerName", typeof(string), typeof(SlexTrigger), new PropertyMetadata(string.Empty));



        /// <summary>
        /// Actual source of this trigger
        /// </summary>
        protected object Source
        {
            get { return (object)GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Source.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.Register("Source", typeof(object), typeof(SlexTrigger), new PropertyMetadata(null));

        #endregion

        #region IsEnabled


        /// <summary>
        /// Checks whether this item is enabled or not
        /// </summary>
        public bool IsEnabled
        {
            get { return (bool)GetValue(IsEnabledProperty); }
            set { SetValue(IsEnabledProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsEnabled.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsEnabledProperty =
            DependencyProperty.Register("IsEnabled", typeof(bool), typeof(SlexTrigger), new PropertyMetadata(true));


        #endregion

        #region Methods

        /// <summary>
        /// Hook the loaded event, so that we can initialize once loading is completed
        /// </summary>
        protected override void OnAttached()
        {

            if (!initialized)
            {
                var obj = AssociatedObject as FrameworkElement;
                obj.Loaded += (sender, args) => { InternalInitialize(); };
            }

            base.OnAttached();
        }


        /// <summary>
        /// Initialize the internally
        /// </summary>
        private void InternalInitialize()
        {
            if (AssociatedObject != null && Source == null)
            {
                if (SourceType==ContextTypeEnum.Element)
                {
                    if (ElementName == "This" || string.IsNullOrEmpty(ElementName))
                    {
                        Source = AssociatedObject;
                    }
                    else
                        Source =  (this.AssociatedObject as FrameworkElement).FindName(ElementName);
                }
                else if (SourceType == ContextTypeEnum.Model)
                {
                    Source = (AssociatedObject as FrameworkElement).DataContext;
                }
                initialized = true;

                Initialize();

                //Initialize all conditions
                if (Conditions != null)
                {
                    Conditions.ToList().ForEach(c =>
                    { c.TriggerContext = this; c.AssociatedObject = this.AssociatedObject; c.InternalInitialize(); });
                }

                //Conditionally invoke supported actions
                foreach (var action in Actions)
                    if (action is SlexTriggerAction)
                    {
                        var a = action as SlexTriggerAction;
                        a.TriggerContext = this;
                        a.InternalInitialize();
                    }
            }

        }

        /// <summary>
        /// Do the trigger initializations here. Should be implemented by all concrete triggers
        /// </summary>
        protected abstract void Initialize();


        /// <summary>
        /// Invoke the actions once this trigger is called
        /// </summary>
        /// <param name="parameter"></param>
        protected virtual void InvokeConditional(object value)
        {
            if (IsEnabled)
            {

                List<SlexTriggerAction> actions = new List<SlexTriggerAction>();
                var otherActions = new List<System.Windows.Interactivity.TriggerAction>();

                if (!SatifyConditions(value, Conditions)) return;
                this.InvokeActions(value);

                //Conditionally invoke supported actions
                foreach (var action in Actions)
                {
                    if (action is SlexTriggerAction)
                        actions.Add(action as SlexTriggerAction);
                    else
                        otherActions.Add(action);
                }

                var actionsToRun = actions.Where(a => SatifyConditions(value, a.Conditions));
                foreach (var c in actionsToRun)
                {
                    c.InvokeConditional(value);
                }

            }

        }


        /// <summary>
        /// Check if the actions satisfies the conditions
        /// </summary>
        /// <param name="value"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public bool SatifyConditions(object value,InvokingConditions conditions)
        {
            if (conditions != null)
            {
                foreach (var a in conditions)
                {
                    object valToCheck = value;
                    if (!string.IsNullOrEmpty(a.Property))                     
                        valToCheck = a.PropertyValue;

                    if (!CheckEqual(a.Value, valToCheck)) return false;
                }
            }

            return true;
        }
        

        /// <summary>
        /// Check equality with a given value
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        protected bool CheckEqual(object given, object target)
        {

            if (given == null || target == null)
            {
                return object.Equals(target, given);
            }
            else
            {
                return object.Equals(target, ConverterHelper.ConvertToType(given, target.GetType()));
            }
        }



        /// <summary>
        /// A set of execution conditions
        /// </summary>
        public InvokingConditions Conditions
        {
            get { return (InvokingConditions)GetValue(ConditionsProperty); }
            set { SetValue(ConditionsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Conditions.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ConditionsProperty =
            DependencyProperty.Register("Conditions", typeof(InvokingConditions), typeof(SlexTrigger), new PropertyMetadata(null));
        

        #endregion

    }

    public enum ContextTypeEnum {     
        Element,
        Model
    }
}
